package com.pzr.soccer.crawler.service.impl;

import com.pzr.soccer.common.consts.RedisConst;
import com.pzr.soccer.crawler.model.CrawlerContext;
import com.pzr.soccer.crawler.service.FootballCrawlerService;
import com.pzr.soccer.crawler.util.CrawlerHelper;
import com.pzr.soccer.crawler.util.ScanGameHelper;
import com.pzr.soccer.match.domain.Game;
import com.pzr.soccer.match.model.GameWrapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.time.LocalTime;
import java.util.*;
import java.util.concurrent.CompletableFuture;


@Service
@Slf4j
//简化注入参数的代码，相当于用constructor的setter注入
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class FootballCrawlerServiceImpl implements FootballCrawlerService {
    final MongoOperations mongoOperations;
    final RedisTemplate<String, Object> redisTemplate;
    final private RabbitTemplate rabbitTemplate;
    final CrawlerHelper crawlerHelper;
    final Queue todayGameStateChangedQueue;
    final Queue todayChangeGameFull;
    final Queue todayChangeGameSlim;

    @Value("${crawler.soccer.scanToday.entryUrl:http://jq.chewtang.com/match/today}")
    private String entryUrl;

    /*
      保存上一场的开赛时间，辅助判断是否跨日（如果本场比赛开赛时间比上场还早，证明跨日了）
     */
    private LocalTime pre_game_begin = LocalTime.parse("00:00");
    private boolean isNextDay = false;

    private final String baseUrl = "http://jq.chewtang.com";

    //异步耗时方法
    @Async
    @Override
    public CompletableFuture<Set<GameWrapper>> scanToday() {
        Set<GameWrapper> wrappers = new HashSet<>();
        long start = System.currentTimeMillis();
        Element body;
        Elements trs;
        try {
            CrawlerContext context=new CrawlerContext(entryUrl,"Crawler:todayGame_rootPage");
            body = crawlerHelper.getBodyByJvppteer(context);
            context.setSelector("#inplay_match_table > tbody.tbody_match > tr");
            trs = crawlerHelper.elements(body,context);
        } catch (Exception e) {
            log.warn("parse error : " + entryUrl);
            return CompletableFuture.completedFuture(wrappers);
        }

        /*begin for---------------------------------------------------->*/
        for (Element tr : trs) {
            ExtractorToday extractor = new ExtractorToday(tr, baseUrl, isNextDay, pre_game_begin);
            final String gameId = extractor.home;
            GameWrapper wrapper = (GameWrapper) redisTemplate.opsForValue().get(RedisConst.Game_Today_Prefix  + gameId);

            if (wrapper == null) {
                wrapper = extractor.buildWrapper();
                wrapper.setNew(true);
                setData(extractor, wrapper, tr);
                if (wrapper.getGameCycle() == GameWrapper.Game_Cycle_Going) {
                    // 设置初始的pair maps
                    String stateUrl = baseUrl + tr.selectFirst("td.text-center.td_analysis > a:nth-child(3)").attr("href");
                    try {
                        CrawlerContext context1=new CrawlerContext(baseUrl,"Crawler:todayGame_statePage:"+wrapper.getGame().getId());
                        Element statePage = crawlerHelper.getBodyByJsoup(context1);
                        Elements scripts = statePage.select("script");
                        // 从实时分析页面取数据
                        ScanGameHelper.setGameInitPairMap(wrapper.getGame(), scripts.toString());
                    } catch (Exception e) {
                        System.out.println("parse stat page error: " + stateUrl);
                        //新建的出错，先不加进来
                        continue;
                    }
                }
            } else {
                wrapper.setNew(false);
                setData(extractor, wrapper, tr);
            }

            //重设
            pre_game_begin = extractor.pre_game_begin;
            isNextDay = extractor.isNextDay;
            wrappers.add(wrapper);
        }
        /*end for <---------------------------------------------------->*/

        handleWrappersAfterFetchToday(wrappers);

        //测试耗时
        long end = System.currentTimeMillis();
        System.out.println("cost :" + (end - start) + " ms");
        return CompletableFuture.completedFuture(wrappers);
    }

    /**
     * fetch 历史数据
     * @param page （一个页面）
     * @param after 早于这个日期的不扫
     * @return @GameWrapper
     */
    @Override
    public Set<GameWrapper> scanHistory(Element page, LocalDate after) {
        Set<GameWrapper> wrappers = new HashSet<>();
        //联赛名称
        String league = page.selectFirst(
                "#content_container > div.main_content > div:nth-child(1) > div.col-sm-6 > ol > li.active").text();
        String yearStr="2022";
        Elements trs = page
                .select("#content_container > div.main_content > div > div > table:nth-child(2) > tbody > tr");

        for(Element tr:trs){
            String trText = tr.text();
            //年月tr
            if (trText != null && trText.length() < 10 && trText.contains("年")) {
                yearStr = trText.substring(0, 4);
                continue;
            }
            ExtractorHistory extractor=new ExtractorHistory(tr,baseUrl,league,yearStr);
            //跳过未结束的比赛
            if(!extractor.currentTime.equals("全")){
                continue;
            }
            //跳过老的比赛
            if(extractor.getActualKickOff().toLocalDate().isBefore(after)){
                continue;
            }
            Game game=findById(extractor.gameId);
            if(game!=null&&!game.getScore().isBlank()){
                continue;
            }
           GameWrapper wrapper=extractor.buildWrapper();
            //设置pairs
            try {
                CrawlerContext context=new CrawlerContext(extractor.getStateUrl(),"crawler_historyGame_stateUrl");
                Element statePage = crawlerHelper.getBodyByJsoup(context);
                Elements scripts = statePage.select("script");
                // 从实时分析页面取数据
                ScanGameHelper.setGameInitPairMap(wrapper.getGame(), scripts.toString());
            } catch (Exception e) {
                System.out.println("parse stat page error: " + extractor.stateUrl);
                //新建的出错，先不加进来
                continue;
            }
            wrappers.add(wrapper);
        }
        return wrappers;
    }

    @Override
    public Set<String> gameListPageAddress(int deep) {

        Set<String> urls = new HashSet<>();
        List<String> continentAddress = List.of("http://jq.chewtang.com/league/continent/3",
                "http://jq.chewtang.com/league/continent/5", "http://jq.chewtang.com/league/continent/2",
                "http://jq.chewtang.com/league/continent/4", "http://jq.chewtang.com/league/continent/10",
                "http://jq.chewtang.com/league/continent/8");

        // 州
        for (String address : continentAddress) {
            CrawlerContext context=new CrawlerContext(address,"Crawler:historyAddress_continentPage");
            Element continent = crawlerHelper.getBodyByJvppteer(context);

            Elements nations = continent.select("#content_container > div > div > div.col-md-10 > ul > li> a");

            // 国家
            for (Element nation : nations) {
                String nationUrl = baseUrl + nation.attr("href");
                System.out.println("url: " + nationUrl + " " + nation.text());
                String exceptionTag="Crawler:historyAddress_nationPage";
                CrawlerContext context1=new CrawlerContext(nationUrl,exceptionTag);
                Element nationPage = crawlerHelper.getBodyByJvppteer(context1);
                Elements leagues = nationPage.select("#content_container > div > div > div.col-md-10 > ul > li > a");

                // 联赛
                for (Element league : leagues) {
                    String leagueUrl = baseUrl + league.attr("href");
                    System.out.println("url: " + leagueUrl + " " + league.text());

                    urls.add(leagueUrl);
                    if (deep <= 1) {
                        continue;
                    }

                    // 拼接其他页面
                    CrawlerContext context2=new CrawlerContext(leagueUrl,"Crawler:historyAddress_gameListPage");
                    Element firstPage = crawlerHelper.getBodyByJvppteer(context2);
                    try {
                        Elements lis = firstPage
                                .select("#content_container > div.main_content > div > div.col-sm-10 > ul > li");
                        Element last = lis.last().selectFirst("a");

                        try {
                            int max = Integer.valueOf(last.attr("href").split(":")[1]);

                            max = max <= deep ? max : deep;
                            for (int i = 2; i <= max; i++) {
                                String url = leagueUrl + "/page:" + String.valueOf(i);
                                System.out.println(url);
                                urls.add(url);
                            }
                        } catch (Exception e) {
                            System.out.println("parse last page index error! extract href ");
                            Elements as = firstPage.select(
                                    "#content_container > div.main_content > div > div.col-sm-10 > ul > li > a");
                            for (Element a : as) {
                                String url = leagueUrl + a.attr("href");
                                urls.add(url);
                                System.out.println(url);
                            }
                        }
                    } catch (Exception e) {
                        System.out.println("pasre other page error");
                    }

                }

            }

        }
        return urls;

    }


    /**
     * 处理已结束的，已过时的（开赛时间离现在已经过去8小时）
     * 发送消息（已结束的ids，有变化的，新开赛的）
     * 更新到redis列表
     *
     * @param wrappers 扫描到的全部
     */

    private void handleWrappersAfterFetchToday(Set<GameWrapper> wrappers) {
        Set<String> keys = redisTemplate.keys(RedisConst.Game_Today_Prefix + "*");
        Set<String> ids = new HashSet<>();
        wrappers.forEach(w -> ids.add(w.getId()));

        /*
         记录状态的改变
         */
        Map<String, Integer> stateMap=new HashMap<>();
        assert keys != null;
        keys.forEach(k -> {
            String gameId = k.replace(RedisConst.Game_Today_Prefix, "");
            //进行--》已结束
            if (!ids.contains(gameId)) {
                GameWrapper wrapperInRedis = (GameWrapper) redisTemplate.opsForValue().get(k);
                if(wrapperInRedis.getGameCycle()!= GameWrapper.Game_Cycle_ended) {
                    wrapperInRedis.setGameCycle(GameWrapper.Game_Cycle_ended);
                    stateMap.put(gameId, GameWrapper.Game_Cycle_ended);
                    redisTemplate.opsForValue().set(RedisConst.Game_Today_Prefix+gameId,wrapperInRedis);
                }
            }
        });

        /*
          发送消息 和 更新redis列表
         */
        for(GameWrapper wrapper:wrappers){

            if(!wrapper.isNew()&&wrapper.getChangedMap().isEmpty()){
                continue;
            }
            //新的或者改变的保存到redis
            redisTemplate.opsForValue().set(RedisConst.Game_Today_Prefix + wrapper.getId(), wrapper);

            //在进行的比赛
            if(wrapper.getGameCycle()!= GameWrapper.Game_Cycle_NotStartYet){
                rabbitTemplate.convertAndSend(todayChangeGameFull.getName(),wrapper);
                System.out.println("send full :"+wrapper.getId());

            }

            //包括未开始的比赛
            wrapper.getGame().getPairCorner().clear();
            wrapper.getGame().getPairScore().clear();
            wrapper.getGame().getPairDanger().clear();
            wrapper.getGame().getPairShot().clear();
            rabbitTemplate.convertAndSend(todayChangeGameSlim.getName(), wrapper);
            System.out.println("send slim :"+wrapper.getId());

        }
        //发送到状态改变 消息列表
        if(!stateMap.isEmpty()){
            rabbitTemplate.convertAndSend(todayGameStateChangedQueue.getName(), stateMap);
            System.out.println("send state :"+stateMap.size()+" pcs");
        }

    }


    private void setPair(int currentTime,String tag,String pairStr, NavigableMap<Integer,int[]>map, GameWrapper wrapper) {
               int[] arr_n = convertToIntNum(pairStr);
               int[] arr_o = map.lastEntry() == null ? new int[]{0, 0} : map.lastEntry().getValue();
               if (arr_n[0] + arr_n[1] > arr_o[0] + arr_o[1]) {
                   map.put(currentTime, arr_n);  //数据有变，保存到game
                   wrapper.getChangedMap().put(tag, arr_n); //保存到wrapper 的changed map
                   System.out.println(String.format("game %s changed at %s",wrapper.getId(),pairStr));
       }


    }


    private int[] convertToIntNum(String pairStr) {
        String[] arrayStr = pairStr.replaceAll("\\s+", "").split("-");

        try {
            if (arrayStr.length == 0) {
                return new int[]{0, 0};
            } else if (arrayStr.length == 1) {
                return new int[]{Integer.parseInt(arrayStr[0]), 0};
            } else {
                if (!arrayStr[0].isBlank()) {
                    return new int[]{Integer.parseInt(arrayStr[0]), Integer.parseInt(arrayStr[1])};
                } else {
                    return new int[]{0, Integer.parseInt(arrayStr[1])};
                }
            }
        } catch (Exception e) {
            log.warn("invalid date:" + e.getMessage());
            return new int[]{0, 0};
        }
    }

    private void setData(ExtractorToday extractorToday, GameWrapper wrapper, Element tr) {
        /*
        set wrapper
         */
        wrapper.setCurrentTimeStr(extractorToday.currentTime);
        wrapper.setGameCycle(extractorToday.gameCycle);
        //设置currentTime_num, and game_stage
        if (extractorToday.currentTime.equals("半")) {
            // 再次是中场休息，跳过
            if (wrapper.getGameStage() == GameWrapper.Game_Stage_MiddleRest) {
                return;
            } else {
                wrapper.setGameStage(GameWrapper.Game_Stage_MiddleRest);
            }
            int currentMin = wrapper.getCurrentMin() < 45 ? 45 : wrapper.getCurrentMin() + 1;
            wrapper.setCurrentMin(currentMin);
        } else if (!extractorToday.currentTime.isBlank()) {
            wrapper.setCurrentMin(Integer.parseInt(extractorToday.currentTime.trim()));
        }
        //设置下半场状态
        if (wrapper.getGameStage() != GameWrapper.Game_Stage_SecondHalf) {
            Element halfCorner_hide_El = tr.selectFirst("td.text-center.match_corner > div > span.span_half_corner.hide");
            //开始下半场
            if (halfCorner_hide_El == null) {
                wrapper.setGameStage(GameWrapper.Game_Stage_SecondHalf);
            }
        }


        /*
         set Game
         */
        Game game = wrapper.getGame();
        game.setHalfCorner(extractorToday.halfCorner);
        game.setScore(extractorToday.score);
        game.setCorner(extractorToday.corner);
        game.setDanger(extractorToday.danger);
        game.setShot(extractorToday.shot);
        wrapper.getChangedMap().clear();
        if(game.getHomeYellow()+game.getHomeRed()+game.getAwayYellow()+game.getAwayRed()!=
           extractorToday.homeYellowCard+extractorToday.homeRedCard+extractorToday.awayYellowCard+extractorToday.awayRedCard){
            game.setHomeYellow(extractorToday.homeYellowCard);
            game.setHomeRed(extractorToday.homeRedCard);
            game.setAwayRed(extractorToday.awayRedCard);
            game.setAwayYellow(extractorToday.awayYellowCard);

               if(!wrapper.isNew()){
                   wrapper.getChangedMap().put("homeYellowCard",extractorToday.homeYellowCard);
                   wrapper.getChangedMap().put("homeRedCard",extractorToday.homeRedCard);
                   wrapper.getChangedMap().put("awayRedCard",extractorToday.awayRedCard);
                   wrapper.getChangedMap().put("awayYellowCard",extractorToday.awayYellowCard);
                   System.out.println("cards changed!");
               }
        }

        //下半场加100分钟
        int verifyCurrentTime = wrapper.getGameStage() == GameWrapper.Game_Stage_SecondHalf ? wrapper.getCurrentMin() + 100 : wrapper.getCurrentMin();
        wrapper.setCurrentMin_revised(verifyCurrentTime);
        if (wrapper.getGameCycle() != GameWrapper.Game_Cycle_NotStartYet) {
            setPair(verifyCurrentTime, "score",extractorToday.score, game.getPairScore(),wrapper);
            setPair(verifyCurrentTime, "shot",extractorToday.shot, game.getPairShot(),wrapper);
            setPair(verifyCurrentTime,"danger" ,extractorToday.danger, game.getPairDanger(),wrapper);
            setPair(verifyCurrentTime, "corner",extractorToday.corner, game.getPairCorner(),wrapper);
        }
    }

    private Game findById(String gameId){
        return mongoOperations.findOne(Query.query(Criteria.where("id").is(gameId)), Game.class);
    }

}


